5.02. Разработка своего API
Разработка своего API
Выбор фреймворка для API на Python
Flask
Flask — это легковесный микрофреймворк, идеально подходящий для небольших проектов, прототипирования и обучения. Он предоставляет минимум встроенных функций, но благодаря своей гибкости позволяет подключать только те компоненты, которые действительно нужны. Для создания API на Flask достаточно определить маршруты с помощью декораторов и вернуть JSON-ответ с помощью функции jsonify.
Преимущества Flask:
- Простота освоения.
- Минимальный объем зависимостей.
- Полный контроль над архитектурой.
- Богатая экосистема расширений (Flask-RESTful, Flask-JWT, Flask-SQLAlchemy).
Недостатки:
- Требуется больше ручной работы для реализации типичных функций (валидация, сериализация, пагинация).
- Отсутствие встроенной поддержки автоматической документации.
FastAPI
FastAPI — современный фреймворк, ориентированный на высокую производительность и удобство разработки. Он основан на стандартах OpenAPI и JSON Schema, что позволяет автоматически генерировать интерактивную документацию (через Swagger UI и ReDoc). FastAPI использует аннотации типов Python для валидации входных данных и сериализации выходных, что делает код самодокументируемым и менее подверженным ошибкам.
Преимущества FastAPI:
- Автоматическая валидация и сериализация.
- Встроенная поддержка асинхронного кода.
- Генерация документации «из коробки».
- Высокая скорость выполнения благодаря использованию Starlette и Pydantic.
Недостатки:
- Требует понимания аннотаций типов и асинхронного программирования.
- Меньше готовых решений для сложных сценариев по сравнению с Django.
Django REST Framework (DRF)
Django REST Framework — расширение для Django, превращающее его в мощный инструмент для построения API. DRF предоставляет сериализаторы, представления, классы разрешений, аутентификации, пагинации и фильтрации. Он отлично подходит для крупных проектов, где уже используется Django в качестве основного фреймворка.
Преимущества DRF:
- Глубокая интеграция с Django ORM.
- Поддержка сложных бизнес-логик и отношений между моделями.
- Готовые решения для аутентификации (Token, Session, OAuth2).
- Расширяемость и зрелая экосистема.
Недостатки:
- Более высокий порог входа.
- Избыточность для простых API.
- Зависимость от архитектуры Django.
Архитектура типичного API на Python
Независимо от выбранного фреймворка, структура проекта обычно включает следующие компоненты:
Маршрутизация — определение URL-путей и сопоставление их с обработчиками (функциями или классами). Каждый маршрут соответствует определённому ресурсу или действию.
Обработчики запросов — функции, которые принимают входные данные, вызывают бизнес-логику и формируют ответ. Они отвечают за проверку прав доступа, обработку ошибок и преобразование данных.
Сериализация и десериализация — процесс преобразования объектов Python в формат, пригодный для передачи по сети (обычно JSON), и обратно. Сериализаторы также выполняют валидацию входных данных.
Бизнес-логика — ядро приложения, содержащее правила обработки данных, взаимодействия с базой данных, вызова внешних сервисов. Эта часть должна быть максимально независимой от деталей реализации API.
Слой доступа к данным — чаще всего это ORM (Object-Relational Mapper), такой как SQLAlchemy (для Flask и FastAPI) или Django ORM. ORM позволяет работать с базой данных через объекты Python, избегая прямого написания SQL-запросов.
Обработка ошибок — централизованный механизм перехвата исключений и возврата стандартизированных сообщений об ошибках. Хороший API никогда не возвращает «голые» трассировки стека.
Аутентификация и авторизация — механизмы проверки личности пользователя и его прав на выполнение операций. Часто используются токены (JWT, Bearer), сессии или API-ключи.
Логирование и мониторинг — запись событий, запросов и ошибок для последующего анализа. Это критически важно для диагностики проблем в продакшене.
Практические шаги: создание первого API
Начнём с минимального, но рабочего примера на FastAPI — фреймворке, который сочетает простоту и мощь. Установка требует только одного пакета:
pip install fastapi uvicorn
Создадим файл main.py:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"message": "Добро пожаловать в ваш первый API!"}
@app.get("/items/{item_id}")
def read_item(item_id: int, q: str = None):
return {"item_id": item_id, "q": q}
Запуск сервера:
uvicorn main:app --reload
После запуска приложение будет доступно по адресу http://127.0.0.1:8000. Документация автоматически генерируется по путям /docs (Swagger UI) и /redoc (ReDoc). Это демонстрирует одно из ключевых преимуществ FastAPI: разработчик получает интерактивную документацию без дополнительных усилий.
Аналогичный пример на Flask выглядит так:
from flask import Flask, jsonify
app = Flask(__name__)
@app.route("/")
def home():
return jsonify({"message": "Добро пожаловать в ваш первый API!"})
@app.route("/items/<int:item_id>")
def get_item(item_id):
return jsonify({"item_id": item_id})
Запуск:
FLASK_APP=main.py flask run
Flask не предоставляет встроенную документацию, но её можно добавить с помощью расширений вроде Flask-Swagger или Flasgger.
В Django REST Framework процесс немного дольше, поскольку требует настройки проекта, но он оправдан при работе с большими системами. После создания проекта и приложения, определяются модели, сериализаторы и представления. DRF позволяет использовать как функциональные, так и классовые представления, а также готовые generic-классы для стандартных операций (ListCreateAPIView, RetrieveUpdateDestroyAPIView и другие).
Работа с данными: модели, ORM и сериализация
Большинство API взаимодействуют с базой данных. В Python это чаще всего делается через ORM — объектно-реляционный маппер, который отображает строки таблиц на объекты Python.
В FastAPI популярным выбором является SQLAlchemy в связке с Pydantic. Pydantic отвечает за валидацию и сериализацию, а SQLAlchemy — за запросы к базе. Пример модели пользователя:
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
Base = declarative_base()
engine = create_engine("sqlite:///./test.db")
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, index=True)
email = Column(String, unique=True, index=True)
Pydantic-схема для входных и выходных данных:
from pydantic import BaseModel
class UserCreate(BaseModel):
name: str
email: str
class UserResponse(BaseModel):
id: int
name: str
email: str
class Config:
from_attributes = True # заменяет orm_mode в новых версиях
Обработчик создания пользователя:
from fastapi import Depends, HTTPException
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.post("/users/", response_model=UserResponse)
def create_user(user: UserCreate, db: SessionLocal = Depends(get_db)):
db_user = db.query(User).filter(User.email == user.email).first()
if db_user:
raise HTTPException(status_code=400, detail="Email уже зарегистрирован")
new_user = User(name=user.name, email=user.email)
db.add(new_user)
db.commit()
db.refresh(new_user)
return new_user
Этот код демонстрирует сквозной поток: клиент отправляет JSON → FastAPI валидирует его через Pydantic → бизнес-логика проверяет уникальность → данные сохраняются через SQLAlchemy → ответ сериализуется обратно в JSON.
В Django REST Framework этот процесс инкапсулирован ещё глубже. Сериализаторы наследуются от serializers.ModelSerializer, а представления — от generics.ListCreateAPIView и подобных. Это ускоряет разработку, но требует понимания внутренней архитектуры фреймворка.
Обработка ошибок и стандартизация ответов
Хороший API возвращает структурированные ошибки. Например, вместо простого текста "Invalid email" клиент получает:
{
"error": {
"code": "invalid_email",
"message": "Формат электронной почты некорректен",
"field": "email"
}
}
В FastAPI это достигается через пользовательские исключения и обработчики:
from fastapi import HTTPException
from fastapi.responses import JSONResponse
from fastapi.exceptions import RequestValidationError
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request, exc):
return JSONResponse(
status_code=400,
content={"error": {"code": "validation_failed", "message": str(exc)}}
)
В Flask используется декоратор @app.errorhandler, в DRF — настройка EXCEPTION_HANDLER в конфигурации.
Стандартизация формата ответов (включая успешные) повышает предсказуемость. Многие команды используют обёртку вида:
{
"data": { ... },
"meta": { "timestamp": "...", "version": "1.0" }
}
или
{
"success": true,
"result": { ... }
}
Такой подход особенно полезен при интеграции с фронтендом или мобильными приложениями.
Аутентификация и безопасность
API должен защищать данные от несанкционированного доступа. Наиболее распространённые методы:
- API-ключи — простой способ идентификации клиента. Передаются в заголовке
X-API-Key. - JWT (JSON Web Token) — токен, содержащий закодированную информацию о пользователе и сроках действия. Подписывается секретным ключом, что гарантирует подлинность.
- OAuth2 — стандарт делегированного доступа, часто используемый для авторизации через сторонние сервисы (Google, GitHub).
FastAPI предоставляет встроенные зависимости для всех этих сценариев. Например, защита эндпоинта с помощью Bearer-токена:
from fastapi.security import HTTPBearer
from fastapi import Depends
security = HTTPBearer()
@app.get("/protected")
def protected_route(token: str = Depends(security)):
# Проверка токена
return {"message": "Доступ разрешён"}
Важно также применять общие меры безопасности:
- Ограничение частоты запросов (rate limiting).
- Валидация всех входных данных.
- Использование HTTPS в продакшене.
- Отключение детальных сообщений об ошибках в production-среде.
Тестирование API
Тестирование — неотъемлемая часть жизненного цикла API. Python предлагает несколько уровней:
- Unit-тесты — проверяют отдельные функции и компоненты (например, сериализаторы).
- Интеграционные тесты — проверяют взаимодействие между слоями (например, вызов эндпоинта с последующей проверкой состояния базы данных).
- End-to-end тесты — имитируют реальные запросы от клиента.
FastAPI поддерживает тестирование через TestClient:
from fastapi.testclient import TestClient
client = TestClient(app)
def test_read_root():
response = client.get("/")
assert response.status_code == 200
assert response.json() == {"message": "Добро пожаловать в ваш первый API!"}
Flask использует test_client(), DRF — APIClient из rest_framework.test.
Автоматизированные тесты должны запускаться при каждом коммите, что обеспечивается CI/CD-системами (GitHub Actions, GitLab CI и другими).
Документирование API
Документация — это не дополнение, а обязательная часть любого публичного или внутреннего API. Без неё интеграция становится медленной, ошибки — частыми, а поддержка — затратной. Современные фреймворки Python позволяют генерировать документацию автоматически, основываясь на аннотациях кода.
OpenAPI (ранее Swagger) — это стандарт описания RESTful API. Он определяет структуру запросов, параметров, тел, ответов, схем данных и возможных ошибок. FastAPI генерирует спецификацию OpenAPI в реальном времени и предоставляет два интерфейса для её просмотра:
- Swagger UI (
/docs) — интерактивный веб-интерфейс, где можно отправлять запросы прямо из браузера. - ReDoc (
/redoc) — более читаемый, документоориентированный просмотрщик.
В Flask документацию можно добавить с помощью расширений:
- Flasgger — генерирует OpenAPI-спецификацию на основе docstring’ов.
- apispec — более гибкий инструмент, совместимый с Marshmallow.
В Django REST Framework используется модуль drf-yasg (Yet Another Swagger Generator) или встроенный SchemaGenerator, который работает с классами представлений и сериализаторами.
Хорошая документация содержит:
- Описание каждого эндпоинта: назначение, примеры использования.
- Спецификацию всех параметров: тип, обязательность, формат, допустимые значения.
- Примеры успешных и ошибочных ответов.
- Информацию об аутентификации.
- Схемы данных в формате JSON Schema.
Автоматическая генерация гарантирует, что документация всегда соответствует текущему состоянию кода. Это особенно важно при частых изменениях.
Версионирование API
Со временем требования к API меняются: добавляются новые поля, удаляются старые, изменяется логика. Чтобы не нарушать работу существующих клиентов, применяется версионирование.
Наиболее распространённые подходы:
-
Версия в URL:
GET /v1/users
GET /v2/users
Этот метод прост, нагляден и легко кэшируется. Он рекомендован большинством крупных платформ (GitHub, Stripe). -
Версия в заголовке запроса:
Accept: application/vnd.myapi.v2+json
Этот способ сохраняет чистоту URL, но сложнее для отладки и требует явной настройки на стороне клиента. -
Версия в параметре строки запроса:
GET /users?version=2
Такой подход менее предпочтителен, так как нарушает принцип идемпотентности и усложняет кэширование.
При проектировании API следует заранее предусмотреть возможность версионирования. Например, в FastAPI можно создать отдельные подприложения для каждой версии:
from fastapi import FastAPI
app = FastAPI()
v1 = FastAPI()
v2 = FastAPI()
app.mount("/v1", v1)
app.mount("/v2", v2)
Каждая версия развивается независимо, но может использовать общие модели и бизнес-логику через импорты.
Развёртывание API
Локальный запуск — лишь первый шаг. Для продакшена требуется надёжное развёртывание.
WSGI vs ASGI
- Flask и Django работают по протоколу WSGI (Web Server Gateway Interface). Для них используются серверы: Gunicorn, uWSGI.
- FastAPI и современные асинхронные приложения используют ASGI (Asynchronous Server Gateway Interface). Серверы: Uvicorn, Daphne, Hypercorn.
Типичный стек для FastAPI в production:
- Uvicorn — ASGI-сервер.
- Gunicorn — менеджер процессов, запускающий несколько воркеров Uvicorn.
- Nginx — обратный прокси, обрабатывающий SSL, статические файлы, ограничение скорости.
- Docker — контейнеризация для единообразного окружения.
Пример Dockerfile для FastAPI:
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["gunicorn", "-k", "uvicorn.workers.UvicornWorker", "main:app"]
Для Flask аналогичный файл использует gunicorn main:app.
Облачные платформы
API можно развернуть на:
- Render, Fly.io, Railway — простые PaaS-решения с минимальной конфигурацией.
- AWS Lambda + API Gateway — бессерверный подход (serverless), подходит для событийных или редко вызываемых API.
- Google Cloud Run, Azure Container Instances — запуск контейнеров без управления инфраструктурой.
Выбор зависит от нагрузки, бюджета и требований к масштабируемости.
Мониторинг и логирование
В продакшене API должен быть наблюдаемым. Это достигается через:
- Логирование — запись входящих запросов, исключений, времени выполнения. В Python используется модуль
logging. Логи направляются в файлы, систему типа ELK (Elasticsearch, Logstash, Kibana) или облачные сервисы (CloudWatch, Datadog). - Метрики — количество запросов, время ответа, частота ошибок. Инструменты: Prometheus + Grafana.
- Трассировка — отслеживание цепочки вызовов в распределённой системе. Используется OpenTelemetry.
FastAPI и Flask легко интегрируются с этими системами через middleware — промежуточные слои, которые перехватывают каждый запрос и ответ.
Пример middleware для логирования в FastAPI:
import time
from fastapi import Request
@app.middleware("http")
async def log_requests(request: Request, call_next):
start_time = time.time()
response = await call_next(request)
process_time = time.time() - start_time
print(f"{request.method} {request.url.path} — {response.status_code} — {process_time:.3f}s")
return response
Такой подход позволяет собирать данные без изменения основной логики приложения.
Лучшие практики проектирования API
- Используйте существительные, а не глаголы в путях:
/users, а не/getUsers. - Соблюдайте иерархию ресурсов:
/users/123/orders/456. - Возвращайте полезные HTTP-статусы: не используйте
200 OKдля ошибок. - Ограничьте размер ответа: применяйте пагинацию (
limit,offsetили курсорную). - Поддерживайте частичное обновление:
PATCHвместо полной замены черезPUT. - Избегайте вложенных JSON-структур глубже двух уровней — это усложняет парсинг.
- Предоставляйте ссылки на связанные ресурсы:
{ "user": "/users/123" }. - Не возвращайте чувствительные данные (пароли, токены) в ответах.
- Поддерживайте CORS, если API вызывается из браузера.
- Пишите тесты до или одновременно с реализацией — это гарантирует стабильность контракта.